Práctica 2: Programación en C y acceso a ficheros mediante la biblioteca estándar
En esta práctica vamos a hacer varios ejercicios orientados a afianzar nuestro conocimiento sobre la programación de sistemas en C y el uso de su biblioteca estándar para operaciones básicas sobre cadenas de caracteres, entrada salida y ficheros.
Se aconseja al alumno que cree un directorio para la práctica con un subdirectorio por ejercicio. En las instrucciones se asume que el ejercicio N se hace en un subdirectorio llamado ejercicioN dentro del directorio común para la práctica.
El archivo ficheros_p2.tar.gz contiene una serie de ficheros que pueden usarse como punto de partida para el desarrollo de los ejercicios de esta práctica, así como unos makefiles que pueden ser usados para la compilación de los distintos proyectos.
Analiza el código del programa show_file.c, que lee byte a byte el contenido de un fichero, cuyo nombre se pasa como parámetro, y lo muestra por pantalla usando funciones de la biblioteca estándar de “C”. Compila y comprueba el funcionamiento correcto del programa. Después modifica el código reemplazando el uso de getc() por el de la función fread() y el uso de putc() por el de la función fwrite(). Consulta las páginas de manual correspondientes.
show_file.c
getc()
fread()
putc()
fwrite()
#include <stdio.h> #include <stdlib.h> int main(int argc, char* argv[]) { FILE* file=NULL; int c,ret; if (argc!=2) { fprintf(stderr,"Usage: %s <file_name>\n",argv[0]); exit(1); } /* Open file */ if ((file = fopen(argv[1], "r")) == NULL) err(2,"The input file %s could not be opened",argv[1]); /* Read file byte by byte */ while ((c = getc(file)) != EOF) { /* Print byte to stdout */ ret=putc((unsigned char) c, stdout); if (ret==EOF){ fclose(file); err(3,"putc() failed!!"); } } fclose(file); return 0; }
Desarrollar dos programas sencillos write_strings.c y read_strings.c que permitan respectivamente escribir y leer de un fichero un conjunto de cadenas de caracteres de longitud variable terminadas por '\0'. Dicho carácter terminador deberá almacenarse en el fichero con el resto de caracteres de cada cadena. Para el desarrollo de los dos programas se utilizarán las siguientes funciones de la biblioteca estándar: fopen(),fclose(), fread(), fwrite(), fseek() y malloc()
write_strings.c
read_strings.c
'\0'
fopen()
fclose()
fseek()
malloc()
El programa write-strings.c aceptará como primer parámetro el nombre de un fichero de texto donde se escribirán los strings pasados a continuación a la línea de comandos (argumento 2, argumento 3, etc.). Si el fichero destino existe, el programa reescribirá su contenido.
write-strings.c
El programa read-strings.c aceptará como parámetro el nombre del fichero de texto donde se almacenen las cadenas de caracteres terminadas en '\0'. Este programa leerá las cadenas y las imprimirá por pantalla separadas por un salto de línea, como se muestra en el siguiente ejemplo de ejecución:
read-strings.c
## Write strings to file usuarioso@debian:~/exercise2$ ./write_strings out London Paris Madrid Barcelona Berlin Lisbon ## Check whether file structure is correct (null-terminated strings) usuarioso@debian:~/exercise2$ $ xxd out 00000000: 4c6f 6e64 6f6e 0050 6172 6973 004d 6164 London.Paris.Mad 00000010: 7269 6400 4261 7263 656c 6f6e 6100 4265 rid.Barcelona.Be 00000020: 726c 696e 004c 6973 626f 6e00 rlin.Lisbon. ## Read strings from file usuarioso@debian:~/exercise2$ ./read_strings out London Paris Madrid Barcelona Berlin Lisbon
Por simplicidad para la implementación del programa read-strings.c, se ha de desarrollar una función auxiliar char* loadstr(FILE* input). Esta función lee una cadena de caracteres terminada en '\0' del fichero cuyo descriptor se pasa como parámetro, reservando dinámicamente la cantidad de memoria adecuada para la cadena leída y retornando dicha cadena. La función tendrá que averiguar primero el número de caracteres de la cadena que comienza a partir de la ubicación actual del puntero de posición del fichero, leyendo carácter a carácter. Una vez detectado el caracter terminador, restaurará el indicador de posición del fichero (moviéndolo hacia atrás) y, finalmente realizará una lectura de la cadena completa.
char* loadstr(FILE* input)
Objetivo: Comprender las diferencias fundamentales entre almacenar y recuperar datos estructurados en ficheros de texto y binarios usando funciones de la biblioteca estándar de C (stdio.h).
stdio.h
Estructura de Datos:
Para este ejercicio, utilizaremos una estructura simple:
#define LABEL_MAX_LEN 15 // Max chars sin contar el '\0' typedef struct { int id; // Identificador entero double value; // Un valor de punto flotante char label[LABEL_MAX_LEN + 1]; // Etiqueta de texto (tamaño fijo) } SimpleRecord;
Parte A: Ficheros de Texto (Human-Readable)
(A.1) Escritura en Fichero de Texto:
Tarea: Crea un programa write_records_text.c.
write_records_text.c
Funcionalidad:
SimpleRecord
label
LABEL_MAX_LEN
argv[1]
"w"
fopen
fprintf
id valor etiqueta\n
fclose
Ejecución de prueba:
> ./write_records_text mis_datos.txt La escritura ha finalizado correctamente > cat mis_datos.txt Barcelona 3.10 1 Madrid 19.77 0 Valencia 7.42 2
(A.2) Lectura de Fichero de Texto:
Tarea: Crea un programa read_records_text.c.
read_records_text.c
"r"
fscanf
record.label
ID: %d, Valor: %.2f, Etiqueta: '%s'\n
> ./read_records_text mis_datos.txt ID:1, Valor:3.10, Etiqueta: 'Barcelona' ID:0, Valor:19.77, Etiqueta: 'Madrid' ID:2, Valor:7.42, Etiqueta: 'Valencia'
Parte B: Ficheros Binarios (Machine-Readable)
(B.1) Escritura en Fichero Binario:
Tarea: Crea un programa write_records_bin.c.
write_records_bin.c
fwrite
sizeof(SimpleRecord)
> ./write_records_bin.c mis_datos.bin La escritura ha finalizado correctamente
(B.2) Lectura de Fichero Binario:
Tarea: Crea un programa read_records_bin.c.
read_records_bin.c
fread
> ./read_records_bin mis_datos.bin ID:1, Valor:3.10, Etiqueta: 'Barcelona' ID:0, Valor:19.77, Etiqueta: 'Madrid' ID:2, Valor:7.42, Etiqueta: 'Valencia'
Parte C: Comparación y Análisis
Acciones: Ejecuta todos los programas creados. Luego, utiliza comandos del shell para comparar los ficheros resultantes:
ls -l mis_datos.txt mis_datos.bin
cat mis_datos.txt
cat mis_datos.bin
xxd mis_datos.bin
hexdump -C mis_datos.bin
Preguntas:
Compara los tamaños de mis_datos.txt y mis_datos.bin. ¿Cuál es más grande o más pequeño? ¿A qué se debe la diferencia? (Considera cómo se almacenan los números y las cadenas en cada formato).
mis_datos.txt
mis_datos.bin
Describe lo que ocurre al intentar ver el fichero binario con cat. ¿Por qué no es legible?
cat
Observando la salida de xxd o hexdump para mis_datos.bin, ¿puedes identificar aproximadamente dónde empieza y termina cada SimpleRecord? ¿Se parece la representación binaria a cómo se alinearían los campos de la struct en memoria?
xxd
hexdump
struct
Menciona brevemente una ventaja y una desventaja de usar el formato de texto y una ventaja y desventaja de usar el formato binario para almacenar estos datos estructurados.
Considera que se reaizara una modificación en la estructura SimpleRecord de tal forma que la etiqueta se representase como char* y su memoria se reservase dinámicamente con malloc, quedando la estructura como sigue:
char*
malloc
typedef struct { int id; // Identificador entero double value; // Un valor de punto flotante char* label; // Etiqueta de texto } SimpleRecord;
Responde a las siguientes preguntas asumiendo que se dispone de un array de estructuras de este tipo (en memoria) correctamente inicializado:
Parte D: Conversión de formatos binario y texto
$ ./conversion [-i t|b] [-o t|b] fichero_entrada fichero_salida